Writing efficient custom code in Web Experimentation
In the Kameleoon back office, there are multiple entry points where you can add custom JavaScript that executes on your website. This article provides specific recommendations for each available entry point. Please note that all Kameleoon scripts run once per page load.
Before implementing custom scripts, consider using Kameleoon Command Queue for optimized execution.
Kameleoon Command Queue
The most efficient way to interact with Kameleoon’s API—whether for converting goals, setting custom data, or triggering events—is to leverage existing triggers in your Tag Manager (e.g., Google Tag Manager) alongside the Kameleoon Command Queue. This should be your primary approach when adding JavaScript to Kameleoon. Instead of writing lengthy JavaScript code directly in Kameleoon, you can achieve the same results with just two lines of code within your Tag Manager.
Examples
- Convert a revenue goal
In your Tag Manager, create a trigger on the confirmation page to capture the revenue, and add the following code:
// Replace kameleoonGoalID and {{revenue}} with your actual values
window.kameleoonQueue = window.kameleoonQueue || [];
window.kameleoonQueue.push(['Goals.processConversion', kameleoonGoalID, {{revenue}}])
For guidance on creating a goal conversion from GTM, see this documentation.
- Set a Custom Data value when the visitor logs in
Instead of writing custom JS in Kameleoon, set the value directly via your Tag Manager:
// Replace "customDataName" and "customDataValue" with your actual values
window.kameleoonQueue = window.kameleoonQueue || [];
window.kameleoonQueue.push(['Data.setCustomData', "customDataName", "customDataName"]);
- Trigger an event when a product is added to the cart
Use this to target visitors based on specific actions:
// Replace "customEventName" with your actual event name
window.kameleoonQueue = window.kameleoonQueue || [];
window.kameleoonQueue.push(['Events.trigger', "customEventName"]);
If kameleoonQueue
cannot be utilized, please refer to the specific recommendations for each available script detailed below.
Global Script & variation scripts
Optimize JavaScript execution with the Activation API
Utilize Kameleoon’s Activation API to apply variations at the appropriate time. This approach helps prevent performance issues and ensures a seamless experience for visitors.
Below are some essential functions of the Activation API that can be used to enhance visitor experience:
runWhenConditionTrue
: Executes the code when a specific condition is met. By default, it runs every 200 milliseconds. However, be careful with conditions that may never resolve, as this can lead to unnecessary looping. Note that the loop will continue running until the function explicitly returnstrue
. Returningfalse
orundefined
will not stop the loop or trigger the callback.
❌ Incorrect usage (Infinite loop risk): The following example will never return true
on all pages other than the product page, causing the loop to run indefinitely:
Kameleoon.API.Core.runWhenConditionTrue(() => {
return window.dataLayer && window.dataLayer.find(layer => layer.pageType === "product");
}, () => {
// Product page logic here
});
✅ Recommended approach: Always return true to exit the loop, and then handle the logic inside the callback:
let pageTypeLayer;
Kameleoon.API.Core.runWhenConditionTrue(() => {
pageTypeLayer = window.dataLayer && window.dataLayer.find(layer => layer.pageType);
return pageTypeLayer;
}, () => {
if (pageTypeLayer?.pageType === "product") {
// Product page logic here
}
});
It is preferable to retrieve information using the URL or cookies/localStorage instead of depending on runWhenConditionTrue
to wait for the dataLayer
or other late-loading objects, like when trying to get information about the page type. This approach ensures faster execution and better performance.
runWhenElementPresent
: This method ensures that your code runs only when a specific element is present in the DOM, avoiding unnecessary delays caused by dynamically loaded elements.
Key considerations
- Uses Mutation Observers by default (recommended for performance).
- If Mutation Observers are disabled on your site, you can specify a polling interval as the 3rd argument of this method.
- If the element is dynamically inserted into the DOM, set
isDynamicElement
totrue
as the 4th argument of this method:
Use the code below sparingly and only when absolutely necessary. Limit its usage to the minimum number of cases and avoid applying it to multiple elements simultaneously to maintain optimal page performance.
Be mindful of the potential for an infinite loop if the source code of the page modifies the element each time it is updated.
Kameleoon.API.Core.runWhenElementPresent('#myDynamicElement', ([elem]) => {
// Logic once the element loaded
}, null, true); // isDynamicElement is set to true
Set your code's scope to specific pages
To optimize performance, make sure your code executes only on the relevant pages. This is particularly important when using loop-based methods like runWhenConditionTrue
and runWhenElementPresent
, as running these methods across the entire site can lead to unnecessary executions and impact performance. For instance, if you are waiting for revenue information in the dataLayer, wrap your code in a condition that checks whether the URL corresponds to the confirmation page. This approach ensures that the code runs only on the confirmation page instead of being triggered site-wide.
Example: Targeting a specific confirmation page before running a loop
if (document.location.href.includes('/confirmation')) {
let revenueLayer;
Kameleoon.API.Core.runWhenConditionTrue(() => {
revenueLayer = window.dataLayer && window.dataLayer.find(layer => layer.revenue);
return revenueLayer;
}, () => {
Kameleoon.API.Goals.processConversion(goalId, revenueLayer.revenue);
});
}
Similarly, if you're waiting for an element to be loaded on the page, make sure the element is expected to be displayed at some point and only execute the script on the relevant page.
if (document.location.href.includes('/subscription')) {
Kameleoon.API.Core.runWhenElementPresent("#form", () => {
// Add the rest of your logic here
});
}
For variation scripts, prefer CSS over JavaScript for visual changes
Using CSS instead of JavaScript results in faster and smoother rendering. CSS can be utilized for hiding or modifying elements, swapping blocks, and changing styles or text.
Handling Single Page Applications (SPAs)
If your experiment is running on a single-page application (SPA) or a dynamically updating page, specific implementation steps are required. Unlike traditional websites, SPAs do not reload entire pages, so Kameleoon needs to identify when content updates happen. Please refer to our guide on setting up an experiment on a single-page application for best practices.
Segments / Triggers
The first best practice is to base your targeting on data that is immediately available when the page loads. This includes information such as the page URL, browser type, device type, and any data stored in cookies or local storage.
If your targeting requires data that is not instantly accessible, follow these guidelines to ensure efficient execution and avoid unnecessary delays.
Custom Javascript Condition
This condition allows you to use custom JavaScript to target visitors. There are two main options available:
Check condition immediately
This script executes every 75 milliseconds before the DOM is fully loaded, and then every 250 milliseconds afterward. By default, it returns undefined
, which causes the script to continue running in a loop until it explicitly returns true
(to include the visitor) or false
(to exclude them). Always ensure that your condition eventually resolves to either true
or false
to prevent unnecessary looping. Here’s an optimized example:
if (window.dataLayer && window.dataLayer.some(layer => layer.pageType == "homepage")) return true;
else if (window.dataLayer && window.dataLayer.some(layer => layer.pageType != "homepage")) return false;
Run the condition asynchronously
Use this script when you need to wait for a response from an external web service or API before targeting the visitor. Once you receive the response, call setTargeting(true) or setTargeting(false). See the code below.
- If you want to target a specific element, use the native condition “Element on the page” instead.
- If you want to wait for information on the page, use the “check condition immediately” option above.
const userId = localStorage.getItem("user_id");
if (!userId) {
setTargeting(false);
}
// Example API endpoint - replace with your actual endpoint
const apiUrl = `https://api.example.com/check-segment?userId=${userId}`;
fetch(apiUrl, { method: "GET", headers: { "Content-Type": "application/json" } })
.then(response => response.json())
.then(data => {
if (data && data.isInSegment) {
setTargeting(true);
} else {
setTargeting(false);
}
})
.catch(error => {
setTargeting(false);
});
Custom event
Using custom events is an effective and passive way to target visitors. Rather than continuously monitoring conditions, this method listens for a specific custom event that you define and triggers targeting when that event occurs. This approach is particularly useful in two key scenarios:
When the targeting logic is complex
It’s better to avoid embedding all the logic directly in a JavaScript condition within the targeting. Instead, you can define a custom event in the Global Script. For example, if you want to target visitors on the cart page who have at least three products in their cart and a total amount above $30, you would trigger an event in the Global Script once these conditions are met, rather than writing all of the logic within the JavaScript segment condition.
When multiple segments share similar logic
If several segments require the same or slightly different variations of the same condition, using a custom event prevents redundant code and enhances maintainability.
Example: You need to target visitors based on different cart amounts. Instead of duplicating the logic in multiple segments, you define a single script in the Global Script and trigger different custom events for each segment:
if (document.location.href.includes('/cart')) {
Kameleoon.API.Core.runWhenElementPresent("#cartContainer", ([cartContainer]) => {
if (cartContainer.length >= 3) {
const totalAmount = Number(cartContainer.querySelector('#totalAmount').innerText);
if (totalAmount >= 90)
Kameleoon.API.Events.trigger("3ArticlesAbove90"); // Segment 1
else if (totalAmount >= 60)
Kameleoon.API.Events.trigger("3ArticlesAbove60"); // Segment 2
else if (totalAmount >= 30)
Kameleoon.API.Events.trigger("3ArticlesAbove30"); // Segment 3
}
});
}
Custom Data
Custom data, like custom events, helps to avoid duplicate code by allowing you to store and reuse values. You can define custom data in the Global Script or in other scripts, and then utilize it within segments. However, custom data differs from custom events in two key ways:
Scope Options: "Page", "Visit", or "Visitor"
Unlike custom events, which only apply to the current page, custom data can persist across multiple pages and sessions.
- Visit Scope: This keeps the value throughout a visitor's session.
- Visitor Scope: This stores the value for up to 365 days, depending on the cookie policy.
For example, if you want to display a banner on all pages once a visitor logs in, you can set a custom data value to "true" with a visit scope. This ensures that the visitor remains targeted with the banner across all pages during their session after logging in.
Enhanced Experiment Insights
Custom data can serve as filters and breakdown criteria on the results page of experiments. This enables deeper insights and allows for more effective analysis of visitor behavior.
To discover all available targeting conditions in the segment builder, please refer to this documentation.